﻿using System;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;

namespace NForth2
{
    class Kernel
    {
        private NForthCore.Engine engine;
        private FieldInfo parser;
        private FieldInfo current_method;

        public Kernel(NForthCore.Engine nf)
        {
            this.engine = nf;
            CreateStacks();
            CreateParser();
            CreateDictionary();
            CreateOperators();
            CreateCompiler();

            //Console.WriteLine(NForthCore.Disassembler.Disassemble(typeof(Kernel).GetMethod("kk")));
        }

        //public static void kk()
        //{
        //    MethodBuilder mb = null;
        //    mb.GetILGenerator().Emit((OpCode)(((FieldInfo)st.Pop()).GetValue(null)));
        //}

        private void CreateStacks()
        {
            engine.CreateMethod(".s");
            ILGenerator il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Call, typeof(NForthCore.Stack).GetMethod("Print"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod(".r");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.RS);
            il.Emit(OpCodes.Call, typeof(NForthCore.Stack).GetMethod("Print"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();
            
            engine.CreateMethod(">r");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.RS);
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("r>");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, engine.RS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("dup");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Dup"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("drop");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Pop);
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("nip");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Nip"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();
        }

        private void CreateParser()
        {
            engine.CreateMethod("parser");
            parser = engine.DefineField("Value");
            ILGenerator il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, parser);
            il.Emit(OpCodes.Call, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("word");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, parser);
            il.Emit(OpCodes.Call, typeof(NForthCore.Parser).GetMethod("NextWord"));
            il.Emit(OpCodes.Call, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("init-parser");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Newobj, typeof(NForthCore.Parser).GetConstructor(new Type[] { }));
            il.Emit(OpCodes.Stsfld, parser);
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.GetMethod("init-parser", "Execute").Invoke(null, null);
        }

        private void CreateDictionary()
        {
            engine.CreateMethod("see");
            ILGenerator il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldsfld, parser);
            il.Emit(OpCodes.Call, typeof(NForthCore.Parser).GetMethod("NextWord"));
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("See"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("current-method");
            current_method = engine.DefineField("Value");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, current_method);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();
            engine.GetField("current-method", "Value").SetValue(null, null);

            engine.CreateMethod("latest");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("get_Latest"));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("create");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Call, engine.GetMethod("word", "Execute"));
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("CreateMethod"));
            il.Emit(OpCodes.Pop);
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldstr, "Execute");
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("DefineMethod"));
            il.Emit(OpCodes.Stsfld, current_method);
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("finish");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, current_method);
            il.Emit(OpCodes.Callvirt, typeof(MethodBuilder).GetMethod("GetILGenerator", new Type[] { }));
            il.Emit(OpCodes.Ldsfld, typeof(OpCodes).GetField("Ret"));
            il.Emit(OpCodes.Callvirt, typeof(ILGenerator).GetMethod("Emit", new Type[] { typeof(OpCode) }));
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("FinishMethod"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("immediate");
            engine.Immediate();
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("Immediate"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();
        }

        public void CreateOperators()
        {
            engine.CreateMethod("find");
            ILGenerator il = engine.DefineMethod("Execute").GetILGenerator();
            Label ok = il.DefineLabel();
            il.DeclareLocal(typeof(object));
            // local = pop
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Stloc_0);
            // getmethod
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldstr, "Execute");
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("GetMethod"));
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Brfalse_S, ok);
            // getnetmethod
            il.Emit(OpCodes.Pop);
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldstr, "Execute");
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("GetMethod"));
            il.Emit(OpCodes.Br_S, ok);
            // ok: push local
            il.MarkLabel(ok);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("number");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Castclass, typeof(string));
            il.Emit(OpCodes.Call, typeof(int).GetMethod("Parse", new Type[] { typeof(string) }));
            il.Emit(OpCodes.Box, typeof(int));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("field.net");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("GetNetField"));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("null");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("'");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Call, engine.GetMethod("word", "Execute"));
            il.Emit(OpCodes.Call, engine.GetMethod("find", "Execute"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("call,");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, current_method);
            il.Emit(OpCodes.Callvirt, typeof(MethodBuilder).GetMethod("GetILGenerator", new Type[] { }));
            il.Emit(OpCodes.Ldsfld, typeof(OpCodes).GetField("Call"));
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Callvirt, typeof(ILGenerator).GetMethod("Emit", new Type[] { typeof(OpCode), typeof(MethodInfo) }));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("callvirt,");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, current_method);
            il.Emit(OpCodes.Callvirt, typeof(MethodBuilder).GetMethod("GetILGenerator", new Type[] { }));
            il.Emit(OpCodes.Ldsfld, typeof(OpCodes).GetField("Callvirt"));
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Callvirt, typeof(ILGenerator).GetMethod("Emit", new Type[] { typeof(OpCode), typeof(MethodInfo) }));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("field,");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, current_method);
            il.Emit(OpCodes.Callvirt, typeof(MethodBuilder).GetMethod("GetILGenerator", new Type[] { }));
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Callvirt, typeof(FieldInfo).GetMethod("GetValue"));
            il.Emit(OpCodes.Unbox_Any, typeof(OpCode));
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Callvirt, typeof(ILGenerator).GetMethod("Emit", new Type[] { typeof(OpCode), typeof(FieldInfo) }));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("opcode,");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, current_method);
            il.Emit(OpCodes.Callvirt, typeof(MethodBuilder).GetMethod("GetILGenerator", new Type[] { }));
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Callvirt, typeof(FieldInfo).GetMethod("GetValue"));
            il.Emit(OpCodes.Unbox_Any, typeof(OpCode));
            il.Emit(OpCodes.Callvirt, typeof(ILGenerator).GetMethod("Emit", new Type[] { typeof(OpCode) }));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("execute");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("Execute"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("null?");
            il = engine.DefineMethod("Execute").GetILGenerator();
            Label isnull = il.DefineLabel();
            ok = il.DefineLabel();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Brtrue_S, isnull);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Br_S, ok);
            il.MarkLabel(isnull);
            il.Emit(OpCodes.Ldc_I4_1);
            il.MarkLabel(ok);
            il.Emit(OpCodes.Box, typeof(int));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("@");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Callvirt, typeof(FieldInfo).GetMethod("GetValue"));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("!");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Ldsfld, engine.DS);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            il.Emit(OpCodes.Callvirt, typeof(FieldInfo).GetMethod("SetValue", new Type[] {typeof(object), typeof(object)}));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();
        }

        public void Interpret()
        {
            NForthCore.Parser parser = (NForthCore.Parser)engine.GetField("parser", "Value").GetValue(null);
            NForthCore.Stack ds = (NForthCore.Stack)engine.GetField("DS", "Value").GetValue(null);

            while (true)
            {
                string word;
                try
                {
                    word = parser.NextWord();
                }
                catch (EndOfStreamException)
                {

                    return;
                }

                if (word == "")
                    continue;

                MethodInfo method = engine.GetMethod(word, "Execute");
                FieldInfo field = engine.GetField(word, "Value");
                MethodInfo method_net = engine.GetNetMethod(word);
                FieldInfo field_net = engine.GetNetField(word);
                bool immediate = (engine.GetWordFlags(word) & (int)NForthCore.WordFlags.F_IMMEDIATE) != 0;

                int state = (int)engine.GetField("state", "Value").GetValue(null);
                if (state == 0 || immediate)
                {
                    if (method != null)
                        engine.Execute(ds, method);
                    else if (field != null)
                        ds.Push(field);
                    else if (method_net != null)
                        engine.Execute(ds, method_net);
                    else if (field_net != null)
                        ds.Push(field_net);
                    else
                    {
                        int number = int.Parse(word);
                        ds.Push(number);
                    }
                }
                else
                {
                    MethodBuilder mb = (MethodBuilder)engine.GetField("current-method", "Value").GetValue(null);
                    if (method != null)
                        engine.CompileMethod(mb, method);
                    else if (field != null)
                        engine.CompileField(mb, field);
                    else if (method_net != null)
                        engine.CompileMethod(mb, method_net);
                    else if (field_net != null)
                        engine.CompileField(mb, field_net);
                    else
                        engine.CompileLiteral(mb, int.Parse(word));
                }
            }
        }

        private void CreateCompiler()
        {
            engine.CreateVariable("state");
            engine.GetField("state", "Value").SetValue(null, 0);

            engine.CreateMethod("variable");
            ILGenerator il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, engine.Dictionary);
            il.Emit(OpCodes.Ldsfld, parser);
            il.Emit(OpCodes.Call, typeof(NForthCore.Parser).GetMethod("NextWord"));
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("CreateVariable"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("[");
            engine.Immediate();
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Box, typeof(int));
            il.Emit(OpCodes.Stsfld, engine.GetField("state", "Value"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();

            engine.CreateMethod("]");
            il = engine.DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Box, typeof(int));
            il.Emit(OpCodes.Stsfld, engine.GetField("state", "Value"));
            il.Emit(OpCodes.Ret);
            engine.FinishMethod();
        }

        public void Include(string file)
        {
            TextReader old_in = Console.In;
            StreamReader sr = new StreamReader(file);

            StringReader new_in = new StringReader(sr.ReadToEnd());
            Console.SetIn(new_in);

            Interpret();

            Console.SetIn(old_in);
            new_in.Close();

            sr.Close();
        }
    }
}
